use anyhow::{bail, Result};
use console::Term;
use futures_util::StreamExt;
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
use libc::geteuid;
use reqwest::header::CONTENT_LENGTH;
use reqwest::{Client};
use std::fs::{read_dir, remove_dir, remove_file, File};
use std::io::Read;
use std::time::{SystemTime};
use std::{ path::Path, process::Command};
use tokio::io::AsyncWriteExt;
use crate::error::DownloadError;
use crate::{error::SystemError};


pub fn get_os_arch() -> Result<String, SystemError> {
    let output = Command::new("uname")
        .arg("-m")
        .output()
        .map_err(|e| SystemError::ArchError(format!("{:?}", e)))?;
    if output.status.success() {
        return Ok(String::from_utf8_lossy(&output.stdout).trim().to_owned());
    } else {
        return Err(SystemError::ArchError(format!("{:?}", output)));
    };
}
pub fn check_dir(path: &str) -> Result<bool> {
    match std::fs::read_dir(path) {
        Ok(c) => {
            return Ok(c.count() > 0);
        }
        Err(_) => {
            bail!("Dir {} don't exist", path)
        }
    }
}
pub fn check_root() -> Result<()> {
    if unsafe { geteuid() } != 0 {
        bail!("root permission is required for this operation");
    }
    Ok(())
}
pub fn recursively_remove_dir<P: AsRef<Path>>(dir: P) -> Result<()> {
    if Path::new(dir.as_ref()).is_dir() {
        match read_dir(dir.as_ref()) {
            Ok(s) => {
                for x in s {
                    let entry = x?;
                    let path = entry.path();
                    if path.is_dir() {
                        recursively_remove_dir(&path)?;
                    } else if path.is_file() {
                        remove_file(path)?;
                    }
                }
            }
            Err(_) => {}
        }
        remove_dir(dir)?;
    }
    Ok(())
}
pub fn get_file_md5<P: AsRef<Path>>(path: P) -> Result<[u8; 16]> {
    let mut f = File::open(path)?;
    let mut buf = Vec::new();
    f.read_to_end(&mut buf)?;
    Ok(md5::compute(buf).0)
}

pub fn read_file_to_string<P:AsRef<Path>>(path:P)->Result<String>{
    let mut f=File::open(path)?;
    let mut buf=String::new();
    f.read_to_string(&mut buf)?;
    Ok(buf)
}
pub async fn download_single_file<P:AsRef<Path>>(client: &Client, url: &str, file_path: P) -> Result<(),DownloadError> {
    let mut stream = client.get(url).send().await.map_err(|e|DownloadError::Network(format!("{:?}",e)))?.bytes_stream();
    let mut f = tokio::fs::OpenOptions::new()
        .write(true)
        .create(true)
        .open(file_path)
        .await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
    while let Some(items) = stream.next().await {
        let bytes = items.map_err(|e|DownloadError::Network(format!("{:?}",e)))?;
        f.write(bytes.as_ref()).await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
    }
    f.flush().await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
    Ok(())
}
#[allow(dead_code)]
pub async fn download_single_file_with_pb<P:AsRef<Path>>(client: &Client,url: &str,file_path: P,msg:&str,pb:&ProgressBar)->Result<(),DownloadError>{
    if let Ok(res) = head_file_size(client, url).await {
        if let Some(len) = res {
            pb.set_length(len);
        }
    }
    pb.set_message(msg.to_owned());

    let mut f = tokio::fs::OpenOptions::new()
    .write(true)
    .create(true)
    .open(file_path)
    .await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
  
    // dbg!("{}",file_path);
    let mut stream = client.get(url).send().await.map_err(|e|DownloadError::Network(format!("{:?}",e)))?.bytes_stream();

    let mut count: u64 = 0;
    let earlier=SystemTime::now();
    let mut duration:u64=0;
 
    while let Some(items) = stream.next().await {
        let bytes = items.map_err(|e|DownloadError::Network(format!("{:?}",e)))?;
        count += bytes.len() as u64;
        let p=SystemTime::now().duration_since(earlier).map_err(|_e| DownloadError::TimeError)?.as_secs();
        if p!=duration{
            duration=p;
            pb.set_position(count);
        }
        f.write(bytes.as_ref()).await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
    }
    f.flush().await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
    Ok(())
}

pub async fn download_single_file_with_pb_decode<P:AsRef<Path>>(client: &Client,url: &str,file_path: P,msg:&str,pb:&ProgressBar)->Result<(),DownloadError>{
    if let Ok(res) = head_file_size(client, url).await {
        if let Some(len) = res {
            pb.set_length(len);
        }
    }
    pb.set_message(msg.to_owned());

    let f = tokio::fs::OpenOptions::new()
    .write(true)
    .create(true)
    .open(file_path)
    .await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
    let mut f_de=async_compression::tokio::write::GzipDecoder::new(f);
    let mut stream = client.get(url).send().await.map_err(|e|DownloadError::Network(format!("{:?}",e)))?.bytes_stream();

    let mut count: u64 = 0;
    let earlier=SystemTime::now();
    let mut duration:u64=0;
 
    while let Some(items) = stream.next().await {
        let bytes = items.map_err(|e|DownloadError::Network(format!("{:?}",e)))?;
        count += bytes.len() as u64;
        let p=SystemTime::now().duration_since(earlier).map_err(|_e| DownloadError::TimeError)?.as_secs();
        if p!=duration{
            duration=p;
            pb.set_position(count);
        }
        f_de.write_all(bytes.as_ref()).await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
    }
    f_de.flush().await.map_err(|e|DownloadError::IoError(format!("{:?}",e)))?;
    Ok(())
}

pub fn get_multi_progress(n:usize)->(MultiProgress,Vec<ProgressBar>){
    let m=MultiProgress::new();
    let (_, width) = Term::stdout().size();
    let style = format!(
        "{{msg:{}}}{{spinner:.green}}[{{bar:{}.cyan/blue}}]{{bytes}}/{{total_bytes}} ({{bytes_per_sec}},{{eta}})",
        width / 3,
        width / 3
    );
    let style = ProgressStyle::with_template(style.as_str())
    .unwrap()
    .progress_chars("#>-");
    let mut v=Vec::new();
    for index in 0..n{
        let pb=m.insert(index, ProgressBar::new(0));
        pb.set_style(style.clone());
        v.push(pb);
    }
    (m,v)
}
async fn head_file_size(client: &Client, url: &str) -> Result<Option<u64>> {
    let res = client.head(url).send().await?;
    return if let Some(v) = res.headers().get(CONTENT_LENGTH) {
        Ok(v.to_str()?.parse::<u64>().ok())
    } else {
        Ok(None)
    };
}
mod tests{

    #[tokio::test]
    async fn test_downlad() {
        use reqwest::ClientBuilder;

        use super::download_single_file;
        let client = ClientBuilder::new().user_agent("rdnf").build().unwrap();
        download_single_file(&client, 
            "https://mirrors.aliyun.com/fedora/releases/37/Everything/x86_64/os/Packages/3/389-ds-base-2.2.3-1.fc37.x86_64.rpm",
         "tests/test.iso").await.unwrap();
        // let p=RepoConfig::read_all("assest/repos.d");
    }
    #[tokio::test]
    async fn test_download_gzip(){
        use futures_util::StreamExt;
        use reqwest::ClientBuilder;
        use tokio::io::AsyncWriteExt;
 
        let fw=tokio::fs::OpenOptions::new().append(true).create(true).open("tests/primary.xml").await.unwrap();
        let client = ClientBuilder::new().user_agent("rdnf").build().unwrap();
        let url="https://mirrors.aliyun.com/fedora/releases/37/Everything/x86_64/os/repodata/54bbae6e9d4cd4865a55f7558daef86574cddc5f2a4f8a0d9c74f946e1a45dd3-primary.xml.gz";
        let mut stream=client.get(url).send().await.unwrap().bytes_stream();

        let mut gz_f=async_compression::tokio::write::GzipDecoder::new(fw);
        
        while let Some(item) = stream.next().await {
            let  bytes=item.unwrap();
            gz_f.write_all(bytes.as_ref()).await.unwrap();
        
        }
        gz_f.flush().await.unwrap();
    }
    #[tokio::test]
    async fn test_download_with(){
        use futures_util::StreamExt;
        use reqwest::ClientBuilder;
        use tokio::io::AsyncWriteExt;
        let mut fw=tokio::fs::OpenOptions::new().append(true).create(true).open("tests/primary.xml.gz").await.unwrap();
        let client = ClientBuilder::new().user_agent("rdnf").build().unwrap();
        let url="https://mirrors.aliyun.com/fedora/releases/37/Everything/x86_64/os/repodata/54bbae6e9d4cd4865a55f7558daef86574cddc5f2a4f8a0d9c74f946e1a45dd3-primary.xml.gz";
        let mut stream=client.get(url).send().await.unwrap().bytes_stream();

        // let mut gz_f=async_compression::tokio::write::GzipDecoder::new(fw);
        
        while let Some(item) = stream.next().await {
            let  bytes=item.unwrap();
            // gz_f.write_all(bytes.as_ref()).await.unwrap();
            fw.write_all(bytes.as_ref()).await.unwrap();
        
        }
        fw.flush().await.unwrap();
        // gz_f.flush().await.unwrap();
    }
    
    
    #[tokio::test]
    async fn test_a(){
 
        use reqwest::ClientBuilder;
       
        use crate::{error::DownloadError};
        let url="https://codecs.fedoraproject.org/openh264/37/x86_64/os/repodata/54a9de010375c9470f3bfa9b594cd36909289019474d9fc74f6f66a7f1450bf0-primary.xml.gz";
        let client = ClientBuilder::new().user_agent("rdnf").build().unwrap();
        let _stream = client.get(url).send().await.map_err(|e|DownloadError::Network(format!("{:?}",e))).unwrap().bytes_stream();
    }
    #[tokio::test]
    async fn test_mutli(){
        use futures_util::StreamExt;
        use reqwest::ClientBuilder;
        use tokio::io::AsyncWriteExt;
        use crate::utils::get_multi_progress;
        use std::time::SystemTime;
        let package = vec![
                "389-ds-base-2.2.3-1.fc37.x86_64.rpm",
                "389-ds-base-devel-2.2.3-1.fc37.x86_64.rpm",
                "389-ds-base-libs-2.2.3-1.fc37.x86_64.rpm",
                "389-ds-base-snmp-2.2.3-1.fc37.x86_64.rpm",
            ];
            let prefix =
                "https://mirrors.aliyun.com/fedora/releases/37/Everything/x86_64/os/Packages/3/";
        let (multi,mut pbs)=get_multi_progress(4);
        let mut handles=Vec::new();
        for i in 0..4 {
            let url=prefix.to_string().clone()+package[i];
            let file_path="tests/".to_string()+package[i];
            let pb=pbs.pop().unwrap();
            pb.set_message(package[i]);
            handles.push(tokio::spawn(async move{
                let client = ClientBuilder::new().user_agent("rdnf").build().unwrap();
                let mut stream = client.get(url).send().await.unwrap().bytes_stream();
                let mut f = tokio::fs::OpenOptions::new()
                    .append(true)
                    .create(true)
                    .open(file_path)
                    .await.unwrap();
                let mut count: u64 = 0;
                let earlier=SystemTime::now();
                let mut duration:u64=0;
                while let Some(items) = stream.next().await {
                    let bytes = items.unwrap();
                    count += bytes.len() as u64;
                    let p=SystemTime::now().duration_since(earlier).unwrap().as_secs();
                    if p!=duration{
                        duration=p;
                        pb.set_position(count);
                    }
                    f.write(bytes.as_ref()).await.unwrap();
                }
                pb.finish_with_message("Done");
            }));
        }
        for ele in handles {
            ele.await.unwrap();
        }
        multi.clear().unwrap();
    }
}

// let m = MultiProgress::new();
// let sty = ProgressStyle::with_template(
//     "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
// )
// .unwrap()
// .progress_chars("##-");

// let pb = m.add(ProgressBar::new(128));
// pb.set_style(sty.clone());

// let pb2 = m.insert_after(&pb, ProgressBar::new(128));
// pb2.set_style(sty.clone());

// let pb3 = m.insert_after(&pb2, ProgressBar::new(1024));
// pb3.set_style(sty);

// m.println("starting!").unwrap();

// let m_clone = m.clone();
// let h1 = thread::spawn(move || {
//     for i in 0..128 {
//         pb.set_message(format!("item #{}", i + 1));
//         pb.inc(1);
//         thread::sleep(Duration::from_millis(15));
//     }
//     m_clone.println("pb1 is done!").unwrap();
//     pb.finish_with_message("done");
// });

// let m_clone = m.clone();
// let h2 = thread::spawn(move || {
//     for _ in 0..3 {
//         pb2.set_position(0);
//         for i in 0..128 {
//             pb2.set_message(format!("item #{}", i + 1));
//             pb2.inc(1);
//             thread::sleep(Duration::from_millis(8));
//         }
//     }
//     m_clone.println("pb2 is done!").unwrap();
//     pb2.finish_with_message("done");
// });

// let m_clone = m.clone();
// let h3 = thread::spawn(move || {
//     for i in 0..1024 {
//         pb3.set_message(format!("item #{}", i + 1));
//         pb3.inc(1);
//         thread::sleep(Duration::from_millis(2));
//     }
//     m_clone.println("pb3 is done!").unwrap();
//     pb3.finish_with_message("done");
// });

// let _ = h1.join();
// let _ = h2.join();
// let _ = h3.join();
// m.clear().unwrap();

